package com.nickandted.sandbox;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.oxm.jaxb.Jaxb2Marshaller;

import javax.annotation.PostConstruct;
import javax.xml.bind.annotation.XmlRootElement;
import java.util.Set;



/**
 * JAXBMarshaller extension that uses Spring API to autodetect and autowire
 * JAXB2 XML root elements. Saves the hassle of having to add them to the Spring
 * configuration manually.
 *
 * Error messages returned by Spring-WS aren't too clear, so welcome addition.
 *
 * @author Jarno
 *
 */
public class ClasspathScanningJAXB2Marshaller extends Jaxb2Marshaller {

    private String basePackage;

    private Logger logger = LoggerFactory.getLogger(ClasspathScanningJAXB2Marshaller.class);

    /**
     * Constructor. Applies Spring specific API in order to get the RootNode
     * annotated classes for JAXB serialization.
     */
    @PostConstruct
    public void init() {
        logger.info("--- Attempting to scan classpath for JAXB2 annotated elements ---");
        this.setClassesToBeBound(getXMLRootClasses());
        logger.info("--- Scan complete ---");
    }

    /**
     * Uses Spring classpath scanning implementation to scan for annotated
     * classes. Classes that have the specific JAXB annotation for rootnodes
     * will be added to the Class [].
     *
     * @return Class array of classes that are annotated as rootnodes
     */
    public final Class<?>[] getXMLRootClasses() {
        ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(
                false);

        scanner.addIncludeFilter(new AnnotationTypeFilter(XmlRootElement.class));

        try {
            Set<BeanDefinition> components = scanner
                    .findCandidateComponents(basePackage);
            Class<?>[] result = new Class[components.size()];
            int i = 0;
            for (BeanDefinition bd : components) {
                result[i] = this.getClass().getClassLoader().loadClass(bd.getBeanClassName());
                logger.info("Found class " + bd.getBeanClassName());
                i++;
            }
            return result;
        }
        // Not gonna happen, we are scanning - not guessing
        catch (ClassNotFoundException cnfe) {
            return new Class<?>[0];
        }
    }

    public void setBasePackage(String basePackage) {
        this.basePackage = basePackage;
    }
}